#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <pwd.h>
#include "http_server.h"
#include "socket.h"

#ifdef HAVE_EPOLL
#include "epoll_event.h"
#elif HAVE_KQUEUE
#include "kqueue_event.h"
#elif HAVE_PORT
#include "port_event.h"
#endif

#ifdef HAVE_LINUX_SENDFILE
#include "linux_write_file_range.h"
#elif HAVE_FREEBSD_SENDFILE
#include "freebsd_write_file_range.h"
#elif HAVE_SOLARIS_SENDFILEV
#include "solaris_write_file_range.h"
#elif HAVE_MMAP
#include "mmap_write_file_range.h"
#else
#include "read_write_file_range.h"
#endif

#include "handle_http_connection.h"
#include "handle_backend_connection.h"
#include "handle_fastcgi_connection.h"
#include "reload_configuration.h"
#include "log.h"

#define MAX_IDLE_TIME 30

#define STATS_FILE    "/tmp/schaefchen.txt"

#ifndef O_LARGEFILE
#define O_LARGEFILE   0
#endif /* O_LARGEFILE */

static char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
static char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

static int get_max_file_descriptors (void);
static int create_connection (http_server_t *http_server, int client, struct sockaddr_in *sin);
static int dump_current_connections (http_server_t *http_server);

int http_server_load_configuration (http_server_t *http_server, configuration_t *conf)
{
	struct stat buf;
	const char *string;
	int port;

	host_list_init (&http_server->host_list);
	http_server->proxy_logfile = NULL;
	http_server->proxy_log_fd = -1;
	http_server->listener = -1;
	http_server->event_manager = -1;
	http_server->running = 0;
	http_server->nfds = 0;
	http_server->index = NULL;
	http_server->events = NULL;
	http_server->http_connections = NULL;
	http_server->interrupted_http_connections = NULL;
	http_server->number_interrupted_http_connections = 0;
	http_server->current_time = 0;
	http_server->handle_alarm = 0;
	http_server->handle_sigusr1 = 0;
	http_server->handle_sighup = 0;
	http_server->logfile = NULL;
	http_server->log_fd = -1;
	buffer_init (&http_server->logbuffer, 512);

	/* Get port to bind to. */
	string = configuration_get_value (conf, "General", "Port", NULL);
	if (!string) {
		fprintf (stderr, "Couldn't get port to bind to.\n");
		return -1;
	}

	port = atoi (string);
	if ((port < 1) || (port > 65535)) {
		fprintf (stderr, "Port to bind to (%s) out of range (1 <= port <= 65535).\n", string);
		return -1;
	}

	http_server->port = port;

	/* Get directory to chroot. */
	string = configuration_get_value (conf, "General", "chroot", NULL);
	if ((!string) || (!*string)) {
		http_server->chroot_directory = NULL;
	} else {
		if (stat (string, &buf) < 0) {
			fprintf (stderr, "chroot directory %s doesn't exist.\n", string);
			return -1;
		}

		if (!S_ISDIR (buf.st_mode)) {
			fprintf (stderr, "%s should be the chroot directory, but it's a file.\n", string);
			return -1;
		}

		http_server->chroot_directory = string;
	}

	/* Get MaxIdleTime. */
	string = configuration_get_value (conf, "General", "MaxIdleTime", NULL);
	if (!string) {
		http_server->max_idle_time = MAX_IDLE_TIME;
	} else {
		http_server->max_idle_time = atoi (string);
		if ((http_server->max_idle_time < 1) || (http_server->max_idle_time > 900)) {
			http_server->max_idle_time = MAX_IDLE_TIME;
		}
	}

	/* Get whether directory listing will be enabled. */
	string = configuration_get_value (conf, "General", "DirectoryListing", NULL);
	if (!string) {
		http_server->dirlisting = 0;
	} else if (strcasecmp (string, "Enabled") == 0) {
		http_server->dirlisting = 1;
	} else if (strcasecmp (string, "Disabled") == 0) {
		http_server->dirlisting = 0;
	} else {
		fprintf (stderr, "DirectoryListing is neither \"Enabled\" nor \"Disabled\"... taking \"Disabled\".\n");
		http_server->dirlisting = 0;
	}

	/* Get whether we will have authentication. */
	string = configuration_get_value (conf, "General", "Authentication", NULL);
	if (!string) {
		http_server->have_authentication = 0;
	} else if (strcasecmp (string, "Enabled") == 0) {
		http_server->have_authentication = 1;
	} else if (strcasecmp (string, "Disabled") == 0) {
		http_server->have_authentication = 0;
	} else {
		fprintf (stderr, "Authentication is neither \"Enabled\" nor \"Disabled\"... taking \"Disabled\".\n");
		http_server->have_authentication = 0;
	}

	/* Get log file. */
	string = configuration_get_value (conf, "General", "ErrorLog", NULL);
	if ((!string) || (!*string)) {
		http_server->logfile = NULL;
	} else {
		http_server->logfile = string;
	}

	/* Get whether we have to log requests. */
	string = configuration_get_value (conf, "General", "LogRequests", NULL);
	if (!string) {
		http_server->log_requests = 1;
	} else if (strcasecmp (string, "Enabled") == 0) {
		http_server->log_requests = 1;
	} else if (strcasecmp (string, "Disabled") == 0) {
		http_server->log_requests = 0;
	} else {
		fprintf (stderr, "LogRequests is neither \"Enabled\" nor \"Disabled\"... taking \"Enabled\".\n");
		http_server->log_requests = 1;
	}

	/* Get user which we will switch to when running as root. */
	http_server->user = configuration_get_value (conf, "General", "User", NULL);

	/* Load host list. */
	if (host_list_load (conf, &http_server->host_list, http_server->dirlisting, http_server->have_authentication, http_server->log_requests) < 0) {
		fprintf (stderr, "Couldn't load host list.\n");
		return -1;
	}

	/* Get whether we will have proxy. */
	string = configuration_get_value (conf, "Proxy", "Status", NULL);
	if (!string) {
		http_server->have_proxy = 0;
	} else if (strcasecmp (string, "Enabled") == 0) {
		http_server->have_proxy = 1;

		/* Get proxy log file. */
		string = configuration_get_value (conf, "Proxy", "Logfile", NULL);
		if ((!string) || (!*string)) {
			http_server->proxy_logfile = NULL;
		} else {
			http_server->proxy_logfile = string;
		}
	} else if (strcasecmp (string, "Disabled") == 0) {
		http_server->have_proxy = 0;
	} else {
		fprintf (stderr, "Proxy is neither \"Enabled\" nor \"Disabled\"... taking \"Disabled\".\n");
		http_server->have_proxy = 0;
	}

	return 0;
}

int http_server_create (http_server_t *http_server)
{
	struct passwd pwbuf;
	struct passwd *pwbufp;
	char *buf;
	long buflen;
	int i;

#ifdef HAVE_EPOLL
	http_server->fn_create_event_manager = epoll_create_event_manager;
	http_server->fn_add_fd = epoll_add_fd;
	http_server->fn_del_fd = epoll_del_fd;
	http_server->fn_del_listener = epoll_del_listener;
	http_server->fn_event_wait = epoll_event_wait;
	http_server->fn_prepare_for_reading = epoll_prepare_for_reading;
	http_server->fn_prepare_for_writing = epoll_prepare_for_writing;
	http_server->fn_remove_notifications = epoll_remove_notifications;
#elif HAVE_KQUEUE
	http_server->fn_create_event_manager = kqueue_create_event_manager;
	http_server->fn_add_fd = kqueue_add_fd;
	http_server->fn_del_fd = kqueue_del_fd;
	http_server->fn_del_listener = kqueue_del_listener;
	http_server->fn_event_wait = kqueue_event_wait;
	http_server->fn_prepare_for_reading = kqueue_prepare_for_reading;
	http_server->fn_prepare_for_writing = kqueue_prepare_for_writing;
	http_server->fn_remove_notifications = kqueue_remove_notifications;
#elif HAVE_PORT
	http_server->fn_create_event_manager = port_create_event_manager;
	http_server->fn_add_fd = port_add_fd;
	http_server->fn_del_fd = port_del_fd;
	http_server->fn_del_listener = port_del_listener;
	http_server->fn_event_wait = port_event_wait;
	http_server->fn_prepare_for_reading = port_prepare_for_reading;
	http_server->fn_prepare_for_writing = port_prepare_for_writing;
	http_server->fn_remove_notifications = port_remove_notifications;
#endif

#ifdef HAVE_LINUX_SENDFILE
	http_server->fn_write_file_range = linux_write_file_range;
#elif HAVE_FREEBSD_SENDFILE
	http_server->fn_write_file_range = freebsd_write_file_range;
#elif HAVE_SOLARIS_SENDFILEV
	http_server->fn_write_file_range = solaris_write_file_range;
#elif HAVE_MMAP
	http_server->fn_write_file_range = mmap_write_file_range;
#else
	http_server->fn_write_file_range = read_write_file_range;
#endif

	/* Get the maximum number of file descriptors. */
	if ((http_server->max_file_descriptors = get_max_file_descriptors ()) < 0) {
		fprintf (stderr, "Couldn't get the maximum number of file descriptors.\n");
		return -1;
	}

	/* Create listener socket. */
	if ((http_server->listener = create_listener_socket (http_server->port)) < 0) {
		fprintf (stderr, "Couldn't create listener socket on port: %u.\n", http_server->port);
		return -1;
	}

	/* Am I root? */
	if ((getuid () == 0) || (geteuid () == 0)) {
		if ((!http_server->user) || (!http_server->user[0])) {
			fprintf (stderr, "A user must be defined when running as root.\n");
			return -1;
		}

		buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
		if (buflen < 0) {
			buflen = 4096;
		}

		buf = (char *) malloc (buflen);
		if (!buf) {
			fprintf (stderr, "Couldn't allocate memory.\n");
			return -1;
		}

		/* Get password file entry for the user. */
#ifndef __sun__
		if ((getpwnam_r (http_server->user, &pwbuf, buf, buflen, &pwbufp) != 0) || (!pwbufp)) {
#else
		if ((pwbufp = getpwnam_r (http_server->user, &pwbuf, buf, buflen)) == NULL) {
#endif

			free (buf);

			fprintf (stderr, "Couldn't get password file entry for user: %s.\n", http_server->user);
			return -1;
		}

		/* If we have chroot directory. */
		if (http_server->chroot_directory) {
			if (chroot (http_server->chroot_directory) < 0) {
				perror ("chroot");

				free (buf);

				return -1;
			}

			if (chdir ("/") < 0) {
				perror ("chdir");

				free (buf);

				return -1;
			}
		}

		if (setgid (pwbufp->pw_gid) < 0) {
			perror ("setgid");

			free (buf);

			return -1;
		}

		if (setuid (pwbufp->pw_uid) < 0) {
			perror ("setuid");

			free (buf);

			return -1;
		}

		free (buf);
	}

	/* Allocate memory for index. */
	http_server->index = (int *) malloc (http_server->max_file_descriptors * sizeof (int));
	if (!http_server->index) {
		fprintf (stderr, "Couldn't allocate memory for index.\n");
		return -1;
	}

	/* Allocate memory for events. */
	http_server->events = (event_t *) malloc (http_server->max_file_descriptors * sizeof (event_t));
	if (!http_server->events) {
		fprintf (stderr, "Couldn't allocate memory for events.\n");
		return -1;
	}

	/* Allocate memory for HTTP connections. */
	http_server->http_connections = (http_connection_t *) malloc (http_server->max_file_descriptors * sizeof (http_connection_t));
	if (!http_server->http_connections) {
		fprintf (stderr, "Couldn't allocate memory for HTTP connections.\n");
		return -1;
	}

	/* Allocate memory for interrupted HTTP connections. */
	http_server->interrupted_http_connections = (http_connection_t **) malloc (http_server->max_file_descriptors * sizeof (http_connection_t *));
	if (!http_server->interrupted_http_connections) {
		fprintf (stderr, "Couldn't allocate memory for interrupted HTTP connections.\n");
		return -1;
	}

	/* If we have log file... */
	if (http_server->logfile) {
		/* Open log file. */
		http_server->log_fd = open (http_server->logfile, O_CREAT | O_WRONLY | O_APPEND | O_LARGEFILE, 0644);
		if (http_server->log_fd < 0) {
			fprintf (stderr, "Couldn't open log file (%s).\n", http_server->logfile);
			return -1;
		}
	}

	/* If we have proxy log file... */
	if (http_server->proxy_logfile) {
		/* Open proxy log file. */
		http_server->proxy_log_fd = open (http_server->proxy_logfile, O_CREAT | O_WRONLY | O_APPEND | O_LARGEFILE, 0644);
		if (http_server->proxy_log_fd < 0) {
			fprintf (stderr, "Couldn't open proxy log file (%s).\n", http_server->proxy_logfile);
			return -1;
		}
	}

	/* Initialize HTTP connections. */
	for (i = 0; i < http_server->max_file_descriptors; i++) {
		http_connection_init (&(http_server->http_connections[i]));
	}

	return 0;
}

void http_server_delete (http_server_t *http_server)
{
	int i;

	http_server->running = 0;

	if (http_server->listener != -1) {
		close (http_server->listener);
		http_server->listener = -1;
	}

	host_list_free (&http_server->host_list);

	if (http_server->event_manager != -1) {
		close (http_server->event_manager);
		http_server->event_manager = -1;
	}

	http_server->nfds = 0;

	if (http_server->index) {
		free (http_server->index);
		http_server->index = NULL;
	}

	if (http_server->events) {
		free (http_server->events);
		http_server->events = NULL;
	}

	if (http_server->http_connections) {
		for (i = 0; i < http_server->max_file_descriptors; i++) {
			http_connection_free (&(http_server->http_connections[i]));
		}

		free (http_server->http_connections);
		http_server->http_connections = NULL;
	}

	if (http_server->interrupted_http_connections) {
		free (http_server->interrupted_http_connections);
		http_server->interrupted_http_connections = NULL;
	}

	http_server->number_interrupted_http_connections = 0;

	http_server->current_time = 0;
	http_server->handle_alarm = 0;
	http_server->handle_sigusr1 = 0;
	http_server->handle_sighup = 0;

	if (http_server->log_fd != -1) {
		close (http_server->log_fd);
		http_server->log_fd = -1;
	}

	if (http_server->proxy_log_fd != -1) {
		close (http_server->proxy_log_fd);
		http_server->proxy_log_fd = -1;
	}

	buffer_free (&http_server->logbuffer);
}

void http_server_copy (http_server_t *dest, http_server_t *src)
{
	host_list_free (&dest->host_list);
	memcpy (&dest->host_list, &src->host_list, sizeof (host_list_t));

	dest->have_proxy = src->have_proxy;
	dest->proxy_logfile = src->proxy_logfile;

	if (src->proxy_logfile) {
		/* If the proxy log file has changed... */
		if (src->proxy_log_fd != -1) {
			if (dest->proxy_log_fd != -1) {
				close (dest->proxy_log_fd);
			}

			dest->proxy_log_fd = src->proxy_log_fd;
		}
	} else {
		if (dest->proxy_log_fd != -1) {
			close (dest->proxy_log_fd);
			dest->proxy_log_fd = -1;
		}
	}

	dest->port = src->port;

	dest->max_idle_time = src->max_idle_time;
	dest->dirlisting = src->dirlisting;
	dest->have_authentication = src->have_authentication;
	dest->log_requests = src->log_requests;
	dest->user = src->user;
	dest->chroot_directory = src->chroot_directory;

	dest->logfile = src->logfile;

	if (src->logfile) {
		/* If the log file has changed... */
		if (src->log_fd != -1) {
			if (dest->log_fd != -1) {
				close (dest->log_fd);
			}

			dest->log_fd = src->log_fd;
		}
	} else {
		if (dest->log_fd != -1) {
			close (dest->log_fd);
			dest->log_fd = -1;
		}
	}

	if (src->listener != -1) {
		http_server.fn_del_listener (dest->listener);
		shutdown (dest->listener, 2);
		close (dest->listener);

		dest->listener = src->listener;
	}
}

int start_http_server (http_server_t *http_server)
{
	event_wait fn_event_wait;
	event_t *events;
	int listener;
	int nfds;
	int client;
	struct sockaddr_in sin;
	socklen_t addrlen;
	time_t now;
	http_connection_t **interrupted_http_connections;
	http_connection_t *http_connection;
	int ret;
	int i;

#if DEBUG
	char peer[20];
#endif

	/* Open event manager file descriptor. */
	if ((http_server->event_manager = http_server->fn_create_event_manager (http_server->max_file_descriptors)) < 0) {
		return -1;
	}

	/* Add listener to the event manager descriptor. */
	if (http_server->fn_add_fd (http_server->listener, EVENT_IN) < 0) {
		return -1;
	}

	http_server->running = 1;

	fn_event_wait = http_server->fn_event_wait;

	events = http_server->events;
	listener = http_server->listener;
	interrupted_http_connections = http_server->interrupted_http_connections;

	/* Save current time. */
	http_server->current_time = time (NULL);
	gmtime_r (&http_server->current_time, &http_server->stm);
	localtime_r (&http_server->current_time, &http_server->local_time);

	if (http_server->log_fd != -1) {
		log_error ("Schaefchen is up and running.");
	}

	while (http_server->running) {
		if (http_server->handle_alarm) {
			now = time (NULL);
			if (http_server->current_time != now) {
				/* Save current time. */
				http_server->current_time = now;
				gmtime_r (&now, &http_server->stm);
				localtime_r (&now, &http_server->local_time);

				/* Check whether some connection has been idle for a very long time. */
				i = 0;
				while (i < http_server->nfds) {
					http_connection = &(http_server->http_connections[http_server->index[i]]);
					if (now - http_connection->last_read_write > http_server->max_idle_time) {
#if DEBUG
						if (http_server->log_fd != -1) {
							log_error ("Removing connection with socket %d for having been idle for a very long time (%lu seconds).", http_connection->sd, now - http_connection->last_read_write);
						}
#endif

						if (http_connection->http_connection_state >= CONNECTING_TO_BACKEND) {
							if (http_connection->fd != -1) {
								if (http_server->http_connections[http_connection->fd].index < i) {
									i = http_server->http_connections[http_connection->fd].index;
								}

								http_server->http_connections[http_connection->fd].fd = -1;
								http_server_remove_connection (http_server, http_connection->fd);
								http_connection->fd = -1;
							}
						}

						http_server_remove_connection (http_server, http_connection->sd);
					} else {
						i++;
					}
				}
			}

			http_server->handle_alarm = 0;
		}

		if (http_server->handle_sigusr1) {
			dump_current_connections (http_server);

			http_server->handle_sigusr1 = 0;
		}

		if (http_server->handle_sighup) {
			reload_configuration ();
			listener = http_server->listener;

			http_server->handle_sighup = 0;
		}

		nfds = fn_event_wait (-1);

		/* For each event... */
		for (i = 0; i < nfds; i++) {
			/* New connection? */
			if (GET_FD (events, i) == listener) {
				addrlen = sizeof (struct sockaddr);
				client = accept (listener, (struct sockaddr *) &sin, &addrlen);
				if (client < 0) {
					if (http_server->log_fd != -1) {
						log_error ("accept: %s.", strerror (errno));
					}

					continue;
				}

#if DEBUG
				if (http_server->log_fd != -1) {
					if (inet_ntop (AF_INET, &sin.sin_addr, peer, sizeof (peer))) {
						log_error ("Connection from: [%s:%u].", peer, ntohs (sin.sin_port));
					}
				}
#endif /* DEBUG */

				if (create_connection (http_server, client, &sin) < 0) {
					close (client);
					break;
				}

#ifdef HAVE_PORT
				/* We need to reassociate the listener. */
				if (http_server->fn_add_fd (listener, EVENT_IN) < 0) {
					close (client);
					break;
				}
#endif /* HAVE_PORT */
			} else {
				http_connection = &(http_server->http_connections[GET_FD (events, i)]);

				/* The connection might have been already removed:
				 *   - It's a client connection that has been proxied and an error occurred
				 *     in the connection to the backend.
				 *   - It's a backend connection and an error occurred in the client connection.
				 */
				if (http_connection->sd < 0) {
					continue;
				}

#ifdef HAVE_PORT
				http_connection->filter *= -1;
#endif /* HAVE_PORT */

				if (http_connection->http_connection_state < CONNECTING_TO_BACKEND) {
					ret = handle_http_connection (http_connection, GET_EVENT (&(events[i])));
				} else if (http_connection->http_connection_state < WAITING_TO_BACKEND) {
					ret = handle_backend_connection (http_connection, GET_EVENT (&(events[i])));
				} else if (http_connection->http_connection_state < CONNECTING_TO_FCGI_SERVER) {
					ret = handle_client_connection (http_connection, GET_EVENT (&(events[i])));
				} else if (http_connection->http_connection_state < WAITING_TO_FCGI_SERVER) {
					ret = handle_fastcgi_connection (http_connection, GET_EVENT (&(events[i])));
				} else {
					ret = handle_fastcgi_client_connection (http_connection, GET_EVENT (&(events[i])));
				}

				if (ret < 0) {
					http_server_remove_connection (http_server, GET_FD (events, i));
				}
#ifdef HAVE_PORT
				else {
					if (http_connection->filter > NO_FILTER) {
						/* We need to reassociate the file descriptor. */
						if (http_connection->filter == -FILTER_READ) {
							if (http_server->fn_prepare_for_reading (http_connection) < 0) {
								http_server_remove_connection (http_server, GET_FD (events, i));
							}
						} else {
							if (http_server->fn_prepare_for_writing (http_connection) < 0) {
								http_server_remove_connection (http_server, GET_FD (events, i));
							}
						}
					}
				}
#endif /* HAVE_PORT */
			}
		}

		/* Handle interrupted HTTP connections. */
		i = 0;
		while (i < http_server->number_interrupted_http_connections) {
			if (interrupted_http_connections[i]->http_connection_state < CONNECTING_TO_BACKEND) {
				ret = handle_http_connection (interrupted_http_connections[i], EVENT_IN | EVENT_OUT);
			} else if (interrupted_http_connections[i]->http_connection_state < WAITING_TO_BACKEND) {
				ret = handle_backend_connection (interrupted_http_connections[i], EVENT_IN | EVENT_OUT);
			} else if (interrupted_http_connections[i]->http_connection_state < CONNECTING_TO_FCGI_SERVER) {
				ret = handle_client_connection (interrupted_http_connections[i], EVENT_IN | EVENT_OUT);
			} else if (interrupted_http_connections[i]->http_connection_state < WAITING_TO_FCGI_SERVER) {
				ret = handle_fastcgi_connection (interrupted_http_connections[i], EVENT_IN | EVENT_OUT);
			} else {
				ret = handle_fastcgi_client_connection (interrupted_http_connections[i], EVENT_IN | EVENT_OUT);
			}

			if (ret < 0) {
				http_server_remove_connection (http_server, interrupted_http_connections[i]->sd);
			}

			if (http_server->number_interrupted_http_connections > 1) {
				interrupted_http_connections[0] = interrupted_http_connections[http_server->number_interrupted_http_connections - 1];
			}

			http_server->number_interrupted_http_connections--;
		}
	}

	return 0;
}

void stop_http_server (http_server_t *http_server)
{
	http_server->running = 0;
}

int get_max_file_descriptors (void)
{
	struct rlimit rlim;

	/* Get the maximum number of file descriptors. */
	if (getrlimit (RLIMIT_NOFILE, &rlim) < 0) {
		perror ("getrlimit");
		return -1;
	}

	return rlim.rlim_cur;
}

int create_connection (http_server_t *http_server, int client, struct sockaddr_in *sin)
{
	http_connection_t *http_connection;
	int flags;

	setnonblocking (client);

	/* Disable the Nagle algorithm. */
	flags = 1;
	setsockopt (client, IPPROTO_TCP, TCP_NODELAY, (const char *) &flags, sizeof (flags));

	/* Add connection to event manager descriptor. */
	if (http_server->fn_add_fd (client, EVENT_IN) < 0) {
		return -1;
	}

	http_server->index[http_server->nfds] = client;

	http_connection = &(http_server->http_connections[client]);

	http_connection->index = http_server->nfds;
	http_connection->sd = client;
	memcpy (&http_connection->sin, sin, sizeof (struct sockaddr_in));

	http_connection->creation = http_server->current_time;
	http_connection->last_read_write = http_server->current_time;

#if defined(HAVE_KQUEUE) || defined(HAVE_PORT)
	http_connection->filter = FILTER_READ;
#endif /* HAVE_KQUEUE || HAVE_PORT */

	http_server->nfds++;

#if DEBUG
	if (http_server->log_fd != -1) {
		log_error ("# connections: %d.", http_server->nfds);
	}
#endif

	return 0;
}

void http_server_remove_connection (http_server_t *http_server, int client)
{
	http_connection_t *http_connection;
	int index;

	http_connection = &(http_server->http_connections[client]);

	/* Remove connection from event manager descriptor. */
#ifdef HAVE_KQUEUE
	if (http_connection->filter != NO_FILTER) {
#elif HAVE_PORT
	if (http_connection->filter < NO_FILTER) {
#endif
		http_server->fn_del_fd (http_connection);
#if defined(HAVE_KQUEUE) || defined(HAVE_PORT)
	}
#endif /* HAVE_KQUEUE || HAVE_PORT */

	/* Save position of HTTP connection in index array. */
	index = http_connection->index;

	http_connection_reset (http_connection);

	http_server->nfds--;

	/* If there are more connections... */
	if (index < http_server->nfds) {
		/* place last connection in the slot we have just freed. */
		http_server->index[index] = http_server->index[http_server->nfds];
		http_server->http_connections[http_server->index[index]].index = index;
	}

#if DEBUG
	if (http_server->log_fd != -1) {
		log_error ("# connections: %d.", http_server->nfds);
	}
#endif
}

int http_server_create_backend_connection (http_server_t *http_server, int client, int fd)
{
	http_connection_t *http_connection;

	/* Add connection to event manager descriptor. */
	if (http_server->fn_add_fd (client, EVENT_OUT) < 0) {
		return -1;
	}

	http_server->index[http_server->nfds] = client;

	http_connection = &(http_server->http_connections[client]);

	http_connection->index = http_server->nfds;
	http_connection->sd = client;
	http_connection->fd = fd;

	http_connection->http_connection_state = CONNECTING_TO_BACKEND;
	http_server->http_connections[fd].http_connection_state = WAITING_TO_BACKEND;

	http_connection->creation = http_server->current_time;
	http_connection->last_read_write = http_server->current_time;

#if defined(HAVE_KQUEUE) || defined(HAVE_PORT)
	http_connection->filter = FILTER_WRITE;
#endif /* HAVE_KQUEUE || HAVE_PORT */

	http_server->nfds++;

#if DEBUG
	if (http_server->log_fd != -1) {
		log_error ("# connections: %d.", http_server->nfds);
	}
#endif

	return 0;
}

int http_server_create_fcgi_connection (http_server_t *http_server, int client, int fd)
{
	http_connection_t *http_connection;

	/* Add connection to event manager descriptor. */
	if (http_server->fn_add_fd (client, EVENT_OUT) < 0) {
		return -1;
	}

	http_server->index[http_server->nfds] = client;

	http_connection = &(http_server->http_connections[client]);

	http_connection->index = http_server->nfds;
	http_connection->sd = client;
	http_connection->fd = fd;

	http_connection->http_connection_state = CONNECTING_TO_FCGI_SERVER;
	http_server->http_connections[fd].http_connection_state = WAITING_TO_FCGI_SERVER;

	http_connection->creation = http_server->current_time;
	http_connection->last_read_write = http_server->current_time;

#if defined(HAVE_KQUEUE) || defined(HAVE_PORT)
	http_connection->filter = FILTER_WRITE;
#endif /* HAVE_KQUEUE || HAVE_PORT */

	http_server->nfds++;

#if DEBUG
	if (http_server->log_fd != -1) {
		log_error ("# connections: %d.", http_server->nfds);
	}
#endif

	return 0;
}

int dump_current_connections (http_server_t *http_server)
{
	http_connection_t *http_connection;
	buffer_t buffer;
	struct tm *stm;
	char peer[20];
	int fd;
	unsigned nfds;
	unsigned i;
	float upload_speed;
	float download_speed;
	time_t diff;
	int ret;

	/* Open file. */
	fd = open (STATS_FILE, O_CREAT | O_TRUNC | O_WRONLY | O_APPEND, 0644);
	if (fd < 0) {
		return -1;
	}

	buffer_init (&buffer, 1024);

	stm = &http_server->stm;
	if (buffer_format (&buffer, "[Report generated %s, %d %s %d %02d:%02d:%02d GMT]\n\n", days[stm->tm_wday], stm->tm_mday, months[stm->tm_mon], 1900 + stm->tm_year, stm->tm_hour, stm->tm_min, stm->tm_sec) < 0) {
		buffer_free (&buffer);
		close (fd);

		return -1;
	}

	/* If there are no connections. */
	nfds = http_server->nfds;
	if (nfds == 0) {
#define NO_CONNECTIONS "No connections.\n"
		if (buffer_append_size_bounded_string (&buffer, NO_CONNECTIONS, sizeof (NO_CONNECTIONS) - 1) < 0) {
			buffer_free (&buffer);
			close (fd);

			return -1;
		}
	} else {
		for (i = 0; i < nfds; i++) {
			http_connection = &(http_server->http_connections[http_server->index[i]]);
			if (inet_ntop (AF_INET, &http_connection->sin.sin_addr, peer, sizeof (peer))) {
				if (buffer_format (&buffer, "[%s]\t[%s]\t[%lu]\t[%lu]\t", peer, http_connection->path.data, http_connection->sent, http_connection->received) < 0) {
					buffer_free (&buffer);
					close (fd);

					return -1;
				}

				diff = http_server->current_time - http_connection->creation;
				if (diff == 0) {
#define NEW_CONNECTION "[0 B/s]\t[0 B/s]\n"
					if (buffer_append_size_bounded_string (&buffer, NEW_CONNECTION, sizeof (NEW_CONNECTION) - 1) < 0) {
						buffer_free (&buffer);
						close (fd);

						return -1;
					}
				} else {
					upload_speed = http_connection->sent / diff;
					if (upload_speed > 1024.0 * 1024.0 * 1024.0) {
						ret = buffer_format (&buffer, "[%.01f GB/s]\t", upload_speed / (1024.0 * 1024.0 * 1024.0));
					} else if (upload_speed > 1024.0 * 1024.0) {
						ret = buffer_format (&buffer, "[%.01f MB/s]\t", upload_speed / (1024.0 * 1024.0));
					} else if (upload_speed > 1024.0) {
						ret = buffer_format (&buffer, "[%.01f KB/s]\t", upload_speed / 1024.0);
					} else {
						ret = buffer_format (&buffer, "[%.01f B/s]\t", upload_speed);
					}

					if (ret < 0) {
						buffer_free (&buffer);
						close (fd);

						return -1;
					}

					download_speed = http_connection->received / diff;
					if (download_speed > 1024.0 * 1024.0 * 1024.0) {
						ret = buffer_format (&buffer, "[%.01f GB/s]\n", download_speed / (1024.0 * 1024.0 * 1024.0));
					} else if (download_speed > 1024.0 * 1024.0) {
						ret = buffer_format (&buffer, "[%.01f MB/s]\n", download_speed / (1024.0 * 1024.0));
					} else if (download_speed > 1024.0) {
						ret = buffer_format (&buffer, "[%.01f KB/s]\n", download_speed / 1024.0);
					} else {
						ret = buffer_format (&buffer, "[%.01f B/s]\n", download_speed);
					}

					if (ret < 0) {
						buffer_free (&buffer);
						close (fd);

						return -1;
					}
				}
			}
		}
	}

	/* Write buffer. */
	ret = (write (fd, buffer.data, buffer.used) == buffer.used)?0:-1;

	buffer_free (&buffer);
	close (fd);

	return ret;
}
